// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Well Known Services - Lists services offered by this host.
 *
 * @author Brian Wellington
 * @see <a href="https://tools.ietf.org/html/rfc1035">RFC 1035: Domain Names - Implementation and
 *     Specification</a>
 */
public class WKSRecord extends Record {
  /**
   * IP protocol identifiers. This is basically copied out of RFC 1010.
   *
   * @see <a href="https://tools.ietf.org/html/rfc1010">RFC 1010: Assigned Numbers</a>
   */
  public static class Protocol {

    private Protocol() {}

    /** Internet Control Message */
    public static final int ICMP = 1;

    /** Internet Group Management */
    public static final int IGMP = 2;

    /** Gateway-to-Gateway */
    public static final int GGP = 3;

    /** Stream */
    public static final int ST = 5;

    /** Transmission Control */
    public static final int TCP = 6;

    /** UCL */
    public static final int UCL = 7;

    /** Exterior Gateway Protocol */
    public static final int EGP = 8;

    /** any private interior gateway */
    public static final int IGP = 9;

    /** BBN RCC Monitoring */
    public static final int BBN_RCC_MON = 10;

    /** Network Voice Protocol */
    public static final int NVP_II = 11;

    /** PUP */
    public static final int PUP = 12;

    /** ARGUS */
    public static final int ARGUS = 13;

    /** EMCON */
    public static final int EMCON = 14;

    /** Cross Net Debugger */
    public static final int XNET = 15;

    /** Chaos */
    public static final int CHAOS = 16;

    /** User Datagram */
    public static final int UDP = 17;

    /** Multiplexing */
    public static final int MUX = 18;

    /** DCN Measurement Subsystems */
    public static final int DCN_MEAS = 19;

    /** Host Monitoring */
    public static final int HMP = 20;

    /** Packet Radio Measurement */
    public static final int PRM = 21;

    /** XEROX NS IDP */
    public static final int XNS_IDP = 22;

    /** Trunk-1 */
    public static final int TRUNK_1 = 23;

    /** Trunk-2 */
    public static final int TRUNK_2 = 24;

    /** Leaf-1 */
    public static final int LEAF_1 = 25;

    /** Leaf-2 */
    public static final int LEAF_2 = 26;

    /** Reliable Data Protocol */
    public static final int RDP = 27;

    /** Internet Reliable Transaction */
    public static final int IRTP = 28;

    /** ISO Transport Protocol Class 4 */
    public static final int ISO_TP4 = 29;

    /** Bulk Data Transfer Protocol */
    public static final int NETBLT = 30;

    /** MFE Network Services Protocol */
    public static final int MFE_NSP = 31;

    /** MERIT Internodal Protocol */
    public static final int MERIT_INP = 32;

    /** Sequential Exchange Protocol */
    public static final int SEP = 33;

    /** CFTP */
    public static final int CFTP = 62;

    /** SATNET and Backroom EXPAK */
    public static final int SAT_EXPAK = 64;

    /** MIT Subnet Support */
    public static final int MIT_SUBNET = 65;

    /** MIT Remote Virtual Disk Protocol */
    public static final int RVD = 66;

    /** Internet Pluribus Packet Core */
    public static final int IPPC = 67;

    /** SATNET Monitoring */
    public static final int SAT_MON = 69;

    /** Internet Packet Core Utility */
    public static final int IPCV = 71;

    /** Backroom SATNET Monitoring */
    public static final int BR_SAT_MON = 76;

    /** WIDEBAND Monitoring */
    public static final int WB_MON = 78;

    /** WIDEBAND EXPAK */
    public static final int WB_EXPAK = 79;

    private static Mnemonic protocols = new Mnemonic("IP protocol", Mnemonic.CASE_LOWER);

    static {
      protocols.setMaximum(0xFF);
      protocols.setNumericAllowed(true);

      protocols.add(ICMP, "icmp");
      protocols.add(IGMP, "igmp");
      protocols.add(GGP, "ggp");
      protocols.add(ST, "st");
      protocols.add(TCP, "tcp");
      protocols.add(UCL, "ucl");
      protocols.add(EGP, "egp");
      protocols.add(IGP, "igp");
      protocols.add(BBN_RCC_MON, "bbn-rcc-mon");
      protocols.add(NVP_II, "nvp-ii");
      protocols.add(PUP, "pup");
      protocols.add(ARGUS, "argus");
      protocols.add(EMCON, "emcon");
      protocols.add(XNET, "xnet");
      protocols.add(CHAOS, "chaos");
      protocols.add(UDP, "udp");
      protocols.add(MUX, "mux");
      protocols.add(DCN_MEAS, "dcn-meas");
      protocols.add(HMP, "hmp");
      protocols.add(PRM, "prm");
      protocols.add(XNS_IDP, "xns-idp");
      protocols.add(TRUNK_1, "trunk-1");
      protocols.add(TRUNK_2, "trunk-2");
      protocols.add(LEAF_1, "leaf-1");
      protocols.add(LEAF_2, "leaf-2");
      protocols.add(RDP, "rdp");
      protocols.add(IRTP, "irtp");
      protocols.add(ISO_TP4, "iso-tp4");
      protocols.add(NETBLT, "netblt");
      protocols.add(MFE_NSP, "mfe-nsp");
      protocols.add(MERIT_INP, "merit-inp");
      protocols.add(SEP, "sep");
      protocols.add(CFTP, "cftp");
      protocols.add(SAT_EXPAK, "sat-expak");
      protocols.add(MIT_SUBNET, "mit-subnet");
      protocols.add(RVD, "rvd");
      protocols.add(IPPC, "ippc");
      protocols.add(SAT_MON, "sat-mon");
      protocols.add(IPCV, "ipcv");
      protocols.add(BR_SAT_MON, "br-sat-mon");
      protocols.add(WB_MON, "wb-mon");
      protocols.add(WB_EXPAK, "wb-expak");
    }

    /** Converts an IP protocol value into its textual representation */
    public static String string(int type) {
      return protocols.getText(type);
    }

    /**
     * Converts a textual representation of an IP protocol into its numeric code. Integers in the
     * range 0..255 are also accepted.
     *
     * @param s The textual representation of the protocol
     * @return The protocol code, or -1 on error.
     */
    public static int value(String s) {
      return protocols.getValue(s);
    }
  }

  public static class Service {
    /**
     * TCP/UDP services. This is basically copied out of RFC 1010, with MIT-ML-DEV removed, as it is
     * not unique, and the description of SWIFT-RVF fixed.
     */
    private Service() {}

    /** Remote Job Entry */
    public static final int RJE = 5;

    /** Echo */
    public static final int ECHO = 7;

    /** Discard */
    public static final int DISCARD = 9;

    /** Active Users */
    public static final int USERS = 11;

    /** Daytime */
    public static final int DAYTIME = 13;

    /** Quote of the Day */
    public static final int QUOTE = 17;

    /** Character Generator */
    public static final int CHARGEN = 19;

    /** File Transfer [Default Data] */
    public static final int FTP_DATA = 20;

    /** File Transfer [Control] */
    public static final int FTP = 21;

    /** Telnet */
    public static final int TELNET = 23;

    /** Simple Mail Transfer */
    public static final int SMTP = 25;

    /** NSW User System FE */
    public static final int NSW_FE = 27;

    /** MSG ICP */
    public static final int MSG_ICP = 29;

    /** MSG Authentication */
    public static final int MSG_AUTH = 31;

    /** Display Support Protocol */
    public static final int DSP = 33;

    /** Time */
    public static final int TIME = 37;

    /** Resource Location Protocol */
    public static final int RLP = 39;

    /** Graphics */
    public static final int GRAPHICS = 41;

    /** Host Name Server */
    public static final int NAMESERVER = 42;

    /** Who Is */
    public static final int NICNAME = 43;

    /** MPM FLAGS Protocol */
    public static final int MPM_FLAGS = 44;

    /** Message Processing Module [recv] */
    public static final int MPM = 45;

    /** MPM [default send] */
    public static final int MPM_SND = 46;

    /** NI FTP */
    public static final int NI_FTP = 47;

    /** Login Host Protocol */
    public static final int LOGIN = 49;

    /** IMP Logical Address Maintenance */
    public static final int LA_MAINT = 51;

    /** Domain Name Server */
    public static final int DOMAIN = 53;

    /** ISI Graphics Language */
    public static final int ISI_GL = 55;

    /** NI MAIL */
    public static final int NI_MAIL = 61;

    /** VIA Systems - FTP */
    public static final int VIA_FTP = 63;

    /** TACACS-Database Service */
    public static final int TACACS_DS = 65;

    /** Bootstrap Protocol Server */
    public static final int BOOTPS = 67;

    /** Bootstrap Protocol Client */
    public static final int BOOTPC = 68;

    /** Trivial File Transfer */
    public static final int TFTP = 69;

    /** Remote Job Service */
    public static final int NETRJS_1 = 71;

    /** Remote Job Service */
    public static final int NETRJS_2 = 72;

    /** Remote Job Service */
    public static final int NETRJS_3 = 73;

    /** Remote Job Service */
    public static final int NETRJS_4 = 74;

    /** Finger */
    public static final int FINGER = 79;

    /** HOSTS2 Name Server */
    public static final int HOSTS2_NS = 81;

    /** SU/MIT Telnet Gateway */
    public static final int SU_MIT_TG = 89;

    /** MIT Dover Spooler */
    public static final int MIT_DOV = 91;

    /** Device Control Protocol */
    public static final int DCP = 93;

    /** SUPDUP */
    public static final int SUPDUP = 95;

    /** Swift Remote Virtual File Protocol */
    public static final int SWIFT_RVF = 97;

    /** TAC News */
    public static final int TACNEWS = 98;

    /** Metagram Relay */
    public static final int METAGRAM = 99;

    /** NIC Host Name Server */
    public static final int HOSTNAME = 101;

    /** ISO-TSAP */
    public static final int ISO_TSAP = 102;

    /** X400 */
    public static final int X400 = 103;

    /** X400-SND */
    public static final int X400_SND = 104;

    /** Mailbox Name Nameserver */
    public static final int CSNET_NS = 105;

    /** Remote Telnet Service */
    public static final int RTELNET = 107;

    /** Post Office Protocol - Version 2 */
    public static final int POP_2 = 109;

    /** SUN Remote Procedure Call */
    public static final int SUNRPC = 111;

    /** Authentication Service */
    public static final int AUTH = 113;

    /** Simple File Transfer Protocol */
    public static final int SFTP = 115;

    /** UUCP Path Service */
    public static final int UUCP_PATH = 117;

    /** Network News Transfer Protocol */
    public static final int NNTP = 119;

    /** HYDRA Expedited Remote Procedure */
    public static final int ERPC = 121;

    /** Network Time Protocol */
    public static final int NTP = 123;

    /** Locus PC-Interface Net Map Server */
    public static final int LOCUS_MAP = 125;

    /** Locus PC-Interface Conn Server */
    public static final int LOCUS_CON = 127;

    /** Password Generator Protocol */
    public static final int PWDGEN = 129;

    /** CISCO FNATIVE */
    public static final int CISCO_FNA = 130;

    /** CISCO TNATIVE */
    public static final int CISCO_TNA = 131;

    /** CISCO SYSMAINT */
    public static final int CISCO_SYS = 132;

    /** Statistics Service */
    public static final int STATSRV = 133;

    /** INGRES-NET Service */
    public static final int INGRES_NET = 134;

    /** Location Service */
    public static final int LOC_SRV = 135;

    /** PROFILE Naming System */
    public static final int PROFILE = 136;

    /** NETBIOS Name Service */
    public static final int NETBIOS_NS = 137;

    /** NETBIOS Datagram Service */
    public static final int NETBIOS_DGM = 138;

    /** NETBIOS Session Service */
    public static final int NETBIOS_SSN = 139;

    /** EMFIS Data Service */
    public static final int EMFIS_DATA = 140;

    /** EMFIS Control Service */
    public static final int EMFIS_CNTL = 141;

    /** Britton-Lee IDM */
    public static final int BL_IDM = 142;

    /** Survey Measurement */
    public static final int SUR_MEAS = 243;

    /** LINK */
    public static final int LINK = 245;

    private static Mnemonic services = new Mnemonic("TCP/UDP service", Mnemonic.CASE_LOWER);

    static {
      services.setMaximum(0xFFFF);
      services.setNumericAllowed(true);

      services.add(RJE, "rje");
      services.add(ECHO, "echo");
      services.add(DISCARD, "discard");
      services.add(USERS, "users");
      services.add(DAYTIME, "daytime");
      services.add(QUOTE, "quote");
      services.add(CHARGEN, "chargen");
      services.add(FTP_DATA, "ftp-data");
      services.add(FTP, "ftp");
      services.add(TELNET, "telnet");
      services.add(SMTP, "smtp");
      services.add(NSW_FE, "nsw-fe");
      services.add(MSG_ICP, "msg-icp");
      services.add(MSG_AUTH, "msg-auth");
      services.add(DSP, "dsp");
      services.add(TIME, "time");
      services.add(RLP, "rlp");
      services.add(GRAPHICS, "graphics");
      services.add(NAMESERVER, "nameserver");
      services.add(NICNAME, "nicname");
      services.add(MPM_FLAGS, "mpm-flags");
      services.add(MPM, "mpm");
      services.add(MPM_SND, "mpm-snd");
      services.add(NI_FTP, "ni-ftp");
      services.add(LOGIN, "login");
      services.add(LA_MAINT, "la-maint");
      services.add(DOMAIN, "domain");
      services.add(ISI_GL, "isi-gl");
      services.add(NI_MAIL, "ni-mail");
      services.add(VIA_FTP, "via-ftp");
      services.add(TACACS_DS, "tacacs-ds");
      services.add(BOOTPS, "bootps");
      services.add(BOOTPC, "bootpc");
      services.add(TFTP, "tftp");
      services.add(NETRJS_1, "netrjs-1");
      services.add(NETRJS_2, "netrjs-2");
      services.add(NETRJS_3, "netrjs-3");
      services.add(NETRJS_4, "netrjs-4");
      services.add(FINGER, "finger");
      services.add(HOSTS2_NS, "hosts2-ns");
      services.add(SU_MIT_TG, "su-mit-tg");
      services.add(MIT_DOV, "mit-dov");
      services.add(DCP, "dcp");
      services.add(SUPDUP, "supdup");
      services.add(SWIFT_RVF, "swift-rvf");
      services.add(TACNEWS, "tacnews");
      services.add(METAGRAM, "metagram");
      services.add(HOSTNAME, "hostname");
      services.add(ISO_TSAP, "iso-tsap");
      services.add(X400, "x400");
      services.add(X400_SND, "x400-snd");
      services.add(CSNET_NS, "csnet-ns");
      services.add(RTELNET, "rtelnet");
      services.add(POP_2, "pop-2");
      services.add(SUNRPC, "sunrpc");
      services.add(AUTH, "auth");
      services.add(SFTP, "sftp");
      services.add(UUCP_PATH, "uucp-path");
      services.add(NNTP, "nntp");
      services.add(ERPC, "erpc");
      services.add(NTP, "ntp");
      services.add(LOCUS_MAP, "locus-map");
      services.add(LOCUS_CON, "locus-con");
      services.add(PWDGEN, "pwdgen");
      services.add(CISCO_FNA, "cisco-fna");
      services.add(CISCO_TNA, "cisco-tna");
      services.add(CISCO_SYS, "cisco-sys");
      services.add(STATSRV, "statsrv");
      services.add(INGRES_NET, "ingres-net");
      services.add(LOC_SRV, "loc-srv");
      services.add(PROFILE, "profile");
      services.add(NETBIOS_NS, "netbios-ns");
      services.add(NETBIOS_DGM, "netbios-dgm");
      services.add(NETBIOS_SSN, "netbios-ssn");
      services.add(EMFIS_DATA, "emfis-data");
      services.add(EMFIS_CNTL, "emfis-cntl");
      services.add(BL_IDM, "bl-idm");
      services.add(SUR_MEAS, "sur-meas");
      services.add(LINK, "link");
    }

    /** Converts a TCP/UDP service port number into its textual representation. */
    public static String string(int type) {
      return services.getText(type);
    }

    /**
     * Converts a textual representation of a TCP/UDP service into its port number. Integers in the
     * range 0..65535 are also accepted.
     *
     * @param s The textual representation of the service.
     * @return The port number, or -1 on error.
     */
    public static int value(String s) {
      return services.getValue(s);
    }
  }

  private byte[] address;
  private int protocol;
  private int[] services;

  WKSRecord() {}

  /**
   * Creates a WKS Record from the given data
   *
   * @param address The IP address
   * @param protocol The IP protocol number
   * @param services An array of supported services, represented by port number.
   */
  public WKSRecord(
      Name name, int dclass, long ttl, InetAddress address, int protocol, int[] services) {
    super(name, Type.WKS, dclass, ttl);
    if (Address.familyOf(address) != Address.IPv4) {
      throw new IllegalArgumentException("invalid IPv4 address");
    }
    this.address = address.getAddress();
    this.protocol = checkU8("protocol", protocol);
    for (int service : services) {
      checkU16("service", service);
    }
    this.services = new int[services.length];
    System.arraycopy(services, 0, this.services, 0, services.length);
    Arrays.sort(this.services);
  }

  @Override
  protected void rrFromWire(DNSInput in) throws IOException {
    address = in.readByteArray(4);
    protocol = in.readU8();
    byte[] array = in.readByteArray();
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < array.length; i++) {
      for (int j = 0; j < 8; j++) {
        int octet = array[i] & 0xFF;
        if ((octet & (1 << (7 - j))) != 0) {
          list.add(i * 8 + j);
        }
      }
    }
    services = new int[list.size()];
    for (int i = 0; i < list.size(); i++) {
      services[i] = list.get(i);
    }
  }

  @Override
  protected void rdataFromString(Tokenizer st, Name origin) throws IOException {
    String s = st.getString();
    address = Address.toByteArray(s, Address.IPv4);
    if (address == null) {
      throw st.exception("invalid address");
    }

    s = st.getString();
    protocol = Protocol.value(s);
    if (protocol < 0) {
      throw st.exception("Invalid IP protocol: " + s);
    }

    List<Integer> list = new ArrayList<>();
    while (true) {
      Tokenizer.Token t = st.get();
      if (!t.isString()) {
        break;
      }
      int service = Service.value(t.value);
      if (service < 0) {
        throw st.exception("Invalid TCP/UDP service: " + t.value);
      }
      list.add(service);
    }
    st.unget();
    services = new int[list.size()];
    for (int i = 0; i < list.size(); i++) {
      services[i] = list.get(i);
    }
  }

  /** Converts rdata to a String */
  @Override
  protected String rrToString() {
    StringBuilder sb = new StringBuilder();
    sb.append(Address.toDottedQuad(address));
    sb.append(" ");
    sb.append(protocol);
    for (int service : services) {
      sb.append(" ").append(service);
    }
    return sb.toString();
  }

  /** Returns the IP address. */
  public InetAddress getAddress() {
    try {
      return InetAddress.getByAddress(address);
    } catch (UnknownHostException e) {
      return null;
    }
  }

  /** Returns the IP protocol. */
  public int getProtocol() {
    return protocol;
  }

  /** Returns the services provided by the host on the specified address. */
  public int[] getServices() {
    return services;
  }

  @Override
  protected void rrToWire(DNSOutput out, Compression c, boolean canonical) {
    out.writeByteArray(address);
    out.writeU8(protocol);
    int highestPort = services[services.length - 1];
    byte[] array = new byte[highestPort / 8 + 1];
    for (int port : services) {
      array[port / 8] |= 1 << (7 - port % 8);
    }
    out.writeByteArray(array);
  }
}
